package remoteprocedurecall;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.SocketException;

import membership.SimpleDBMembership;

import session.ErrorCodes;
import session.SessionData;
import session.SessionDataTable;
import values.VALUES;

public class RPCServer extends Thread{

	public static final int CALL_ID_INDEX = 0;
	public static final int OP_CODE_INDEX = 1;
	public static final int GET_SESSION_ID_INDEX = 2;
	public static final int PUT_SESSION_INDEX = 2;
	public static final int GET_VERSION_INDEX = 3;
	
	private SessionDataTable sessions;
	
	/**
	 * Create an new Datagram Socket for the SSM Server and register the server with the group.
	 * 
	 */
	@Override
	public void run() {
		try{
		  DatagramSocket rpcSocket = new DatagramSocket(findFreePort(), InetAddress.getLocalHost());
		  initServer(new SocketAddress(rpcSocket.getLocalPort(), rpcSocket.getLocalAddress()));
		  while(true) {
		    byte[] inBuf = new byte[1024];
		    DatagramPacket recvPkt = new DatagramPacket(inBuf, inBuf.length);
		    rpcSocket.receive(recvPkt);
		    InetAddress returnAddr = recvPkt.getAddress();
		    int returnPort = recvPkt.getPort();
		    // here inBuf contains the callID and operationCode
		    byte[] outBuf = computeResponse(recvPkt.getData(), recvPkt.getLength());
		    //System.out.println("receiving " + new String(recvPkt.getData()));
		    // here outBuf should contain the callID
		    DatagramPacket sendPkt = new DatagramPacket(outBuf, outBuf.length,
		    	returnAddr, returnPort);
		    rpcSocket.send(sendPkt);
		  }
		}
		catch (SocketException e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		catch (IOException e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}

	private int findFreePort()
	throws IOException {
		ServerSocket server =
			new ServerSocket(0);
		int port = server.getLocalPort();
		server.close();
		return port;
	}

	
	/**
	 * Create the session db table, and initialize the local SimpleDBMembership instance
	 * @param socketAddress 
	 */
	private void initServer(SocketAddress socketAddress) {
		sessions = new SessionDataTable(VALUES.BLOCK_TIMEOUT, VALUES.SESSION_TIMEOUT);
		SimpleDBMembership.getInstance(socketAddress.getAddress().getHostAddress(),socketAddress.getLocalPort());
		System.out.println("init server " + socketAddress.getAddress().getHostAddress()+":"+socketAddress.getLocalPort());
	}

	private byte[] executeGetCode(String callID, String opCode, String sessionID, String versionStr){
		SessionData sessionData = sessions.getSession(sessionID, Integer.parseInt(versionStr.trim()));
		if(sessionData == null){
			return (callID + RPCClient.RPC_SEP + RPCClient.OP_SNF).getBytes();
		}
		int version = Integer.parseInt(versionStr.trim());
		String sessionInfo = sessionData.sessionDataToString();
		String reply = null;
		
		if(sessionData.version != version)
			reply = callID + RPCClient.RPC_SEP + RPCClient.OP_VE;
		else
			reply = callID + RPCClient.RPC_SEP + opCode + RPCClient.RPC_SEP + sessionInfo;
		
		return reply.getBytes();
	}
	
	private byte[] computeResponse(byte[] data, int length) {
		String s = new String(data);
		String[] dataBlocks = s.split(RPCClient.RPC_SEP);
		String callID = dataBlocks[CALL_ID_INDEX].trim();
		String opCode = dataBlocks[OP_CODE_INDEX].trim();
		
		//System.out.println("Received " + opCode);
		if(opCode.equals(RPCClient.OP_GET)){
			return executeGetCode(callID, opCode, dataBlocks[GET_SESSION_ID_INDEX], dataBlocks[GET_VERSION_INDEX]);
		}
		else if(opCode.startsWith(RPCClient.OP_PUT)){
			return	executePutCode(callID, opCode, dataBlocks);
		} else if(opCode.startsWith(RPCClient.OP_PING)){
			return ("0" + RPCClient.RPC_SEP + RPCClient.OP_PONG).getBytes();
		}
		return null;
	}


	private byte[] executePutCode(String callID, String opCode,
			String[] dataBlocks) {
		SessionData sessionData = new SessionData(dataBlocks[PUT_SESSION_INDEX]);
		ErrorCodes ec;
		if(sessions.getSession(sessionData.sessionID,sessionData.version)!=null){
			synchronized(sessions.getSession(sessionData.sessionID,sessionData.version)){
				ec = sessions.setSession(sessionData);
			}	
		} else {
			ec = sessions.setSession(sessionData);
		}
		String reply = null;
		 if(ec == ErrorCodes.OLD_VERSION){
			 reply = callID + RPCClient.RPC_SEP + RPCClient.OP_PVE;
		 }
		 else{
			 reply = callID + RPCClient.RPC_SEP + opCode;
		 }
		 
		return reply.getBytes();
	}

}
